<?php

class ar
{

    /**
     * Дополняет второй аргумент,
     * недостающими параметрами из первого и «чистит» результат по схеме.
     *
     * @param array $defaultData
     * @param array $data
     * @param array $schema, см. prepareSchema()
     * @return array
     */
    static function mergeBySchema($_data1, $_data2, $_schema)
    {
        $schema = array();
        try {
            $schema = self::prepareSchema($_schema);
        } catch (Exception $e) {
            throw new Exception();
        }

        $data1 = array();
        if (is_array($_data1)) {
            $data1 =& $_data1;
        }

        $data2 = array();
        if (is_array($_data2)) {
            $data2 =& $_data2;
        }

        $result = array();
        if (empty($schema) || empty($data2)) {
            $result =& $data1;
        } else {
            $result = self::__mergeBySchema($data1, $data2, $schema);
        }

        return $result;
    }

    protected static function __mergeBySchema($data1, $data2, $schema)
    {
        $result = $data1;
        foreach ($schema as $key => $value) {
            if (array_key_exists($key, $data2)) {
                if (is_array($value)) {
                    if (!array_key_exists($key, $data1)) {
                        $data1[$key] = array();
                    }
                    $m = __FUNCTION__;
                    $result[$key] = self::$m($data1[$key], $data2[$key], $schema[$key]);
                } else {
                    $result[$key] = $data2[$key];
                }

            }
        }

        return $result;
    }

    /**
     * «Чистит» исходный массив по схеме. Т. е. лишние элементы удаляються.
     *
     * @param array $data
     * @param array $schema, см. prepareSchema()
     * @return array
     */
    static function prepareBySchema($data, $_schema)
    {
        return self::mergeBySchema(array(), $data, $_schema);
    }

    /**
     * Возвращает нормализованную схему.
     * Схема может быть представленна ввиде массива ключей,
     * либо содержать древовидную структуру, листья которой — ключи массива.
     * Можно, также, для определения всего одного ключа указать его,
     * в качестве параметра.
     *
     * @param mixed $schema
     * @return array
     */
    static function prepareSchema($schema)
    {
        $result = array();
        if (is_array($schema)) {
            foreach ($schema as $key => $value) {
                if (is_scalar($value) && is_int($key)) {
                    $result[$value] = true;
                } else {
                    $m = __FUNCTION__;
                    $result[$key] = self::$m($value);
                }
            }
        } elseif (is_scalar($schema)) {
            $result = array($schema => true);
        } else {
            throw new Exception();
        }

        return $result;
    }

    /**
     * Возвращает массив ключей из второго аргумента-массива,
     * значения при которых: отличны от соответствующих в первом аргументе.
     *
     * @param array $data1
     * @param array $data2
     * @return array
     */
    static function keysModifiedValues($data1, $data2)
    {
        if (!is_array($data1) || !is_array($data1)) {
            throw new Exception();
        }

        $result = array();
        foreach ($data1 as $key => $value) {
            if (array_key_exists($key, $data2)) {
                if ($value !== $data2[$key]) {
                    $result[] = $key;
                }
            }
        }

        return $result;
    }

    /**
     * Возвращает ссылку на значение,
     * заданное путём (от родителя к потомку), указанным первым аргументом-массивом.
     * В случае отсуцтвия — генерирует исключение.
     *
     * @param array $path
     * @param array $array
     * @return mixed
     */
    static function &valueRefByPath($path, &$array)
    {
        $parent =& $array;
        foreach ($path as $key) {
            if (array_key_exists($key, $parent)) {
                $parent =& $parent[$key];
            } else {
                throw new Exception();
            }
        }

        return $parent;
    }

    /**
     * Тоже, что и valueRefByPath(), но возвращает не ссылку, а значение.
     * В случае отсуцтвия — возвращает null.
     *
     * @param array $path
     * @param array $array
     * @return mixed
     */
    static function valueByPath($path, &$array)
    {
        try {
            $m = __FUNCTION__;
            return self::$m($path, $array);
        } catch (Exception $e) {
            return null;
        }
    }

    static function concat($a, $b)
    {
        if (! self::isVector($a) || ! self::isVector($b)) {
            throw new ArgException();
        }
        foreach ($b as $item) {
            $a[] = $item;
        }

        return $a;
    }

    static function keys($value)
    {
        return array_keys($value);
    }

    static function values($value)
    {
        return array_values($value);
    }

    static function length($value)
    {
        return count($value);
    }

    /**
     * Проверяет, является ли аргумент массивом.
     *
     * @param mixed $value
     * @return bool
     */
    static function isArray($value)
    {
        return is_array($value);
    }

    /**
     * Проверяет, является ли аргумент пронумерованным массивом.
     * Важное свойство массивов:
     * — Все ключи-числа приводятся к int.
     * — Все ключи-строки приводятся к int, 
     *   но если преобразование реализуемо — остаются строками.
     *
     * @param mixed $value
     * @return bool
     */
    static function isVector($value)
    {
        if (self::isArray($value)) {
            $i = 0;
            foreach ($value as $k => $v) if ($k !== $i++) return false;
            return true;
        }
        return false;
    }

    /**
     * Проверяет, является ли аргумент хешем.
     *
     * @param mixed $value
     * @return bool
     */
    static function isHash($value)
    {
        return self::isArray($value) && ! self::isVector($value);
    }
    
    static 
    function
    keyProc($callback, $array)
    {
      	$newArray = array();
      	foreach ($array as $key => $value)
      	{
			      $newArray[call_user_func($callback, $key)] = $value;
    	  }
    	
    	  return $newArrray;
    }
    
    static 
    function
    regexFilterByKeys($pattern, $array)
    {
      	$newArray = array();
      	foreach ($array as $key => $value)
      	{
        		if (preg_match($pattern, $array))
        		{
    				    $newArray[$key] = $value;
            }
      	}
      	
      	return $newArrray;
    }    
    
    /**
     * Возвращает хеш, содержащий только элементы с указанным ключями.
     * Можно также, указывать пары ключ-значение, 
     * для переименования исходных ключей новыми.
     *
     * @param array $data
     * @param array $schema
     * @return array
     */
    static 
    function
    filterByKeysAndRenameKeys($data, $schema)
    { $RES = array();
    
        foreach ($schema as $key => $value)
        {
            $new_key = is_string($key) ? $key : $schema[$key];
            
            if (array_key_exists($key, $data))
            {
                throw new TheException("Don’t present array-element at key “{$key}”.");
            }
            if (array_key_exists($new_key, $RES))
            {
                throw new TheException("Invalid schema: duplicate key “{$new_key}”.");
            }
            $RES[$new_key] = $data[$key];
        }
        
        return $RES;
    }
    
}

?>